package com.tanhua.dubbo.es.api.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.dubbo.config.annotation.Service;
import com.tanhua.dubbo.server.api.UserLocationApi;
import com.tanhua.dubbo.server.pojo.UserLocation;
import com.tanhua.dubbo.server.vo.PageInfo;
import com.tanhua.dubbo.server.vo.UserLocationVo;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.GeoDistanceQueryBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.sort.GeoDistanceSortBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.*;

import javax.annotation.PostConstruct;
import java.util.HashMap;
import java.util.Map;

@Service(version = "1.0.0")
@Slf4j
public class UserLocationApiImpl implements UserLocationApi {

    @Autowired
    private ElasticsearchTemplate elasticsearchTemplate;

    /**
     * 初始化索引库
     *@PostConstruct 该注解被用来修饰一个非静态的void（）方法。
     * 被@PostConstruct修饰的方法会在服务器加载Servlet的时候运行，
     * 并且只会被服务器执行一次。PostConstruct在构造函数之后执行，init（）方法之前执行。
     * spring生命周期中的init，在对象创建时调用此方法
     */
    @PostConstruct
    public void initIndex(){
        //判断索引库是否存在，如果不存在，需要创建
        //基于UserLocatio中的@Document(indexName = "tanhua", type = "user_location", shards = 6, replicas = 2)创建索引
        if (!this.elasticsearchTemplate.indexExists("tanhua")){
            this.elasticsearchTemplate.createIndex(UserLocation.class);
        }
        //判断表是否存在，如果不存在，需要创建
        //"tanhua"：数据库   "user_location" ：表（映射文档）
        if (!this.elasticsearchTemplate.typeExists("tanhua", "user_location")){
            this.elasticsearchTemplate.putMapping(UserLocation.class);
        }
    }
    /*
    ES基本步骤：
        //2.构建查询请求对象，指定查询索引名称
        SearchRequest searchRequest = new SearchRequest("goods");
        //4.创建查询条件构建器
        + sourceBuilder = new SearchSourceBuilder();
        //6.创建查询条件
        QueryBuilder query = QueryBuilders.matchAllQuery();
        //5.指定查询条件
        sourceBuilder.query(query);
        //8.分页
        sourceBuilder.from(0);
        sourceBuilder.size(100);
        //3.添加查询条件构建器
        searchrequest.source(sourceBuilder);
        //1.查询，获取结果
        SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
     */
    @Override
    public Boolean updateUserLocation(Long userId, Double longitude, Double latitude, String address) {
        //查询地理位置数据，如果不存在，需要新增，如果存在，更新数据
        try {
            GetQuery query = new GetQuery();
            query.setId(String.valueOf(userId));
            UserLocation userLocation = this.elasticsearchTemplate.queryForObject(query, UserLocation.class);
            if (ObjectUtil.isEmpty(userLocation)){
                userLocation = new UserLocation();
                userLocation.setUserId(userId);
                userLocation.setAddress(address);
                userLocation.setCreated(System.currentTimeMillis());
                userLocation.setUpdated(userLocation.getCreated());
                userLocation.setLastUpdated(userLocation.getCreated());
                userLocation.setLocation(new GeoPoint(latitude, longitude));

                //保存数据到ES中

                IndexQuery indexQuery = new IndexQueryBuilder().withObject(userLocation).build();
                this.elasticsearchTemplate.index(indexQuery);
            }else {
                //有数据更新数据
                //更新的字段
                Map<String,Object> map = new HashMap<>();
                map.put("address",address);
                map.put("updated",System.currentTimeMillis());
                map.put("lastUpdated",userLocation.getUpdated());
                map.put("location",new GeoPoint(latitude, longitude));
                UpdateRequest updateRequest = new UpdateRequest().doc(map);
                UpdateQuery updateQuery = new UpdateQueryBuilder()
                        .withId(String.valueOf(userId))
                        .withClass(UserLocation.class)
                        .withUpdateRequest(updateRequest).build();
                this.elasticsearchTemplate.update(updateQuery);
            }

            return true;
        } catch (Exception e) {
            log.error("更新地理位置失败~ userId = " + userId + ", longitude = " + longitude + ", " +
                    "latitude = " + latitude + ", address = " + address, e);
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 查询地理位置
     *
     * @param userId
     * @return
     */
    @Override
    public UserLocationVo queryByUserId(Long userId) {
        GetQuery getQuery = new GetQuery();
        getQuery.setId(String.valueOf(userId));
        UserLocation userLocation = this.elasticsearchTemplate.queryForObject(getQuery, UserLocation.class);
        if (ObjectUtil.isEmpty(userLocation)){
            return null;
        }
        return UserLocationVo.format(userLocation);
    }

    /**
     * 根据位置搜索附件
     *
     * @param longitude 经度
     * @param latitude  纬度
     * @param distance  距离(米)
     * @param page      页数
     * @param pageSize  页面大小
     * @return
     */
    @Override
    public PageInfo<UserLocationVo> queryUserFromLocation(Double longitude,
                                                          Double latitude,
                                                          Double distance,
                                                          Integer page,
                                                          Integer pageSize) {
        PageInfo<UserLocationVo> pageInfo = new PageInfo<>();
        pageInfo.setPageNum(page);
        pageInfo.setPageSize(pageSize);
        //5.2地理位置所需要的查询的字段条件
        String fieldName = "location";

        //2.NativeSearchQueryBuilder()   构建本地查询构建器构建
        NativeSearchQueryBuilder searchSourceBuilder = new NativeSearchQueryBuilder();

        //3.1分页
        PageRequest pageRequest = PageRequest.of(page - 1, pageSize);
        //3.构建分页条件
        searchSourceBuilder.withPageable(pageRequest);

        //4.1 构造条件
        BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();

        //5.构造查询条件
        //5.1地理位置
        GeoDistanceQueryBuilder geoDistanceQueryBuilder = new GeoDistanceQueryBuilder(fieldName);
        //5.1.1 指定中心点
        geoDistanceQueryBuilder.point(new GeoPoint(latitude, longitude));
        //5.1.2指定范围半径，单位：公里
        geoDistanceQueryBuilder.distance(distance / 1000,DistanceUnit.KILOMETERS);

        //4.2设置query 构造query完成
        boolQueryBuilder.must(geoDistanceQueryBuilder);

        //4.构造Query
        searchSourceBuilder.withQuery(boolQueryBuilder);

        //6.构建排序条件，
        GeoDistanceSortBuilder geoDistanceSortBuilder = new GeoDistanceSortBuilder(fieldName,
                latitude, longitude);
        //6.1 由近到远
        geoDistanceSortBuilder.order(SortOrder.ASC);
        geoDistanceSortBuilder.unit(DistanceUnit.KILOMETERS);

        //3.2添加排序条件
        searchSourceBuilder.withSort(geoDistanceSortBuilder);
        //1.分页查询当前附近的人，按照距离正序排序，中心点为当前用户位置
        AggregatedPage<UserLocation> aggregatedPage = this.elasticsearchTemplate.queryForPage(
                searchSourceBuilder.build(),
                UserLocation.class);
        if (CollUtil.isEmpty(aggregatedPage.getContent())){
            return pageInfo;
        }
        pageInfo.setRecords(UserLocationVo.formatToList(aggregatedPage.getContent()));
        return pageInfo;
    }
}
